﻿using System;
using System.Drawing;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

using LowLevelGraphics.ColorSpaces;

namespace LowLevelGraphics.Histograms
{
    /// <summary>
    /// Histogram
    /// </summary>
    public class Histogram
    {
        protected float[] m_aData = null;
        private int m_nBinCount1;
        private int m_nBinCount2;
        private int m_nBinCount3;

        /// <summary>
        /// Initializes a new instance of the <see cref="Histogram"/> class.
        /// </summary>
        public Histogram(int _nBinCount)
        {

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Histogram"/> class.
        /// </summary>
        /// <param name="_nBinCount">The _n bin count.</param>
        /// <param name="_bitmap">The _bitmap.</param>
        public Histogram(int _nBinCount, UnsafeBitmap _bitmap)
            : this(_nBinCount, _bitmap,
                new Rectangle(0, 0, _bitmap.Width, _bitmap.Height)
            , 1, 1)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Histogram"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        public Histogram(UnsafeBitmap _bitmap)
            : this(_bitmap, 1, 1)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Histogram"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="nXStep">The n X step.</param>
        /// <param name="nYStep">The n Y step.</param>
        public Histogram(UnsafeBitmap _bitmap, int nXStep, int nYStep)
            : this(10, _bitmap, new Rectangle(0, 0, _bitmap.Width, _bitmap.Height), nXStep, nYStep)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Histogram"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_rect">The _rect.</param>
        /// <param name="_nXStep">The _n X step.</param>
        /// <param name="_nYStep">The _n Y step.</param>
        public Histogram(int _nBinCount, UnsafeBitmap _bitmap, Rectangle _rect, int _nXStep, int _nYStep)
        {
            m_aData = new float[_nBinCount];
            m_nBinCount1 = _nBinCount;
            m_nBinCount2 = _nBinCount;
            m_nBinCount3 = _nBinCount;
            //Create(_bitmap, _rect, _nXStep, _nYStep);
            Create(_bitmap, _nBinCount, _nBinCount, _nBinCount);
        }

        /// <summary>
        /// Gets or sets the bin count.
        /// </summary>
        /// <value>The bin count.</value>
        public int BinCount1
        {
            get { return m_nBinCount1; }
            set { m_nBinCount1= value; }
        }

        /// <summary>
        /// Gets or sets the bin count.
        /// </summary>
        /// <value>The bin count.</value>
        public int BinCount2
        {
            get { return m_nBinCount2; }
            set { m_nBinCount2 = value; }
        }

        /// <summary>
        /// Gets or sets the bin count.
        /// </summary>
        /// <value>The bin count.</value>
        public int BinCount3
        {
            get { return m_nBinCount3; }
            set { m_nBinCount3 = value; }
        }

        /// <summary>
        /// Gets data at index _nIndex
        /// </summary>
        /// <param name="_nIndex"></param>
        /// <returns></returns>
        public float this[int _nIndex]
        {
            get { return m_aData[_nIndex]; }
        }

        /// <summary>
        /// Finds the mean.
        /// </summary>
        /// <returns></returns>
        public float FindMean()
        {
            float m = 0;

            for (int i = 0; i < m_aData.Length; i++)
            {
                m += m_aData[i];
            }

            return m / m_aData.Length;
        }

        /// <summary>
        /// Gets the data.
        /// </summary>
        /// <value>The data.</value>
        public float[] Data
        {
            get { return m_aData; }
        }

        /// <summary>
        /// Normalizes the specified total.
        /// </summary>
        /// <param name="total">The total.</param>
        public virtual void Normalize(float total)
        {
            for (int i = 0; i < m_aData.Length; i++)
            {
                m_aData[i] = m_aData[i] / total;
            }
        }

        /// <summary>
        /// Creates the specified BMP.
        /// </summary>
        /// <param name="bmp">The BMP.</param>
        /// <param name="numBinsCh1">The num bins CH1.</param>
        /// <param name="numBinsCh2">The num bins CH2.</param>
        /// <param name="numBinsCh3">The num bins CH3.</param>
        protected virtual void Create(UnsafeBitmap bmp, int numBinsCh1, int numBinsCh2, int numBinsCh3)
        {
            int nBinIndexCount = numBinsCh1 * numBinsCh2 * numBinsCh3;

            this.m_aData = new float[nBinIndexCount];

            float fTotal = 0;
            int nIndex = 0;

            UnsafeBitmap fastBitmap = bmp;
            Color pPixel = Color.Transparent;

            int nHeight = fastBitmap.Height;
            int nWidth = fastBitmap.Width;

            for (int y = 0; y < nHeight; y++)
            {
                for (int x = 0; x < nWidth; x++)
                {
                    pPixel = fastBitmap[x, y];
                    //get the bin index for the current pixel colour
                    nIndex = GetSingleBinIndex(numBinsCh1, numBinsCh2, numBinsCh3, pPixel);
                    this.Data[nIndex] += 1;
                    fTotal += 1;
                }
            }

            //normalise
            if (fTotal > 0)
            {
                Normalize(fTotal);
            }
        }

        /// <summary>
        /// Gets the index of the single bin.
        /// </summary>
        /// <param name="binCount1">The bin count1.</param>
        /// <param name="binCount2">The bin count2.</param>
        /// <param name="binCount3">The bin count3.</param>
        /// <param name="pixel">The pixel.</param>
        /// <returns></returns>
        public int GetSingleBinIndex(int binCount1, int binCount2, int binCount3, Color pixel)
        {
            int nIndex = 0;

            //find the index                
            int i1 = GetBinIndex(binCount1, pixel.R, 255);
            int i2 = GetBinIndex(binCount2, pixel.G, 255);
            int i3 = GetBinIndex(binCount3, pixel.B, 255);
            nIndex = i1 + i2 * binCount1 + i3 * binCount1 * binCount2;

            return nIndex;
        }

        /// <summary>
        /// Gets the index of the bin.
        /// </summary>
        /// <param name="_nBinCount">The _n bin count.</param>
        /// <param name="_nColorValue">The _n color value.</param>
        /// <param name="_fMaxValue">The _f max value.</param>
        /// <returns></returns>
        public int GetBinIndex(int _nBinCount, float _nColorValue, float _fMaxValue)
        {
            int nIndex = (int)(_nColorValue * (float)_nBinCount / _fMaxValue);
            if (nIndex >= _nBinCount)
                nIndex = _nBinCount - 1;

            return nIndex;
        }

        /// <summary>
        /// Draws the specified model.
        /// </summary>
        /// <param name="_nHeight">Height of the _n.</param>
        /// <param name="_nWidth">Width of the _n.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data.</param>
        /// <param name="color">The color.</param>
        /// <param name="binRange">The bin range.</param>
        protected void Draw(int _nHeight, int _nWidth, PaintEventArgs e, Color color, int binRange)
        {
            Pen pen = new Pen(color, 1);
            SolidBrush brush = new SolidBrush(color);
            float[] aCopy = new float[this.Data.Length];
            Array.Copy(this.Data, aCopy, this.Data.Length);
            Array.Sort(aCopy);

            float fMax = aCopy[aCopy.Length - 1];
            float fScale = _nHeight / fMax;

            if (float.IsNaN(fScale))
            {
                fScale = 1;
            }

            //Approximation: divide by total bins and remove 4 from width to account for picbox border
            float w = (float)(_nWidth - 4) / (float)(this.Data.Length);
            for (int nCount = 0; nCount < this.Data.Length; nCount++)
            {
                if (this.Data[nCount] > 0)
                {
                    e.Graphics.DrawRectangle(pen, (float)nCount * w, (float)_nHeight - (this.Data[nCount] * fScale), w, this.Data[nCount] * fScale);
                    e.Graphics.FillRectangle(brush, (float)nCount * w, (float)_nHeight - (this.Data[nCount] * fScale), w, this.Data[nCount] * fScale);
                }
            }
        }

        /// <summary>
        /// Bhattacharyyas the specified hist1.
        /// </summary>
        /// <param name="_histogram1">The _histogram1.</param>
        /// <param name="_histogram2">The _histogram2.</param>
        /// <returns></returns>
        public static float Bhattacharyya(Histogram _histogram1, Histogram _histogram2)
        {
            double coeff = 0;
            float t = 0;
            for (int i = 0; i < _histogram1.Data.Length; i++)
            {
                coeff += Math.Sqrt((double)_histogram1.Data[i] * (double)_histogram2.Data[i]);
                t += _histogram1.Data[i];
            }
            return (float)coeff;
        }

        /// <summary>
        /// Gets the count.
        /// </summary>
        /// <value>The count.</value>
        public int Count
        {
            get { return m_aData.Length; }
        }
    }
}


